#include <linux/sched.h>
#include <linux/kernel.h>
#include <asm/segment.h>

#include <fcntl.h>
#include <errno.h>
#include <const.h>
#include <sys/stat.h>
#include <string.h>

#define MAY_EXEC    1
#define MAY_WRITE   2
#define MAY_READ    4

static int permission(struct m_inode *inode, int mask)
{
    int mode = inode->i_mode;

    if (inode->i_dev && !inode->i_nlinks)   // 链接数至少为1
        return 0;
    else if (current->euid == inode->i_uid) // 当前进程属于创建文件的用户
        mode >>= 6;
    else if (current->egid == inode->i_gid) // 当前进程所属的用户与创建文件的用户在同一用户组
        mode >>= 3;
    if (((mode & mask & 0007) == mask) || suser())
        return 1;
    return 0;
}

static int match(int len, const char *name, struct dir_entry *de)
{
    if (!de || !de->inode || len > NAME_LEN)
        return 0;
    if (len < NAME_LEN && de->name[len])    // 解析的文件名长度比目录项的文件名长度小
        return 0;
    return strncmp(name, de->name, len) == 0;
}

static struct buffer_head *find_entry(struct m_inode **dir,
    const char *name, int namelen, struct dir_entry **res_dir)
{
    int entries;
    int block, i;
    struct buffer_head *bh;
    struct dir_entry *de;

    if (!namelen)
        return NULL;
    if (namelen > NAME_LEN)
        namelen = NAME_LEN;

    entries = (*dir)->i_size / (sizeof(struct dir_entry));
    *res_dir = NULL;

    block = (*dir)->i_zone[0];
    if (!block)
        return NULL;
    
    bh = bread((*dir)->i_dev, block);
    if (!bh)
        return NULL;
    
    i = 0;
    de = (struct dir_entry *)bh->b_data;
    while (i < entries) {
        if (match(namelen, name, de)) {
            *res_dir = de;
            return bh;
        }
        de++;
        i++;
    }
    brelse(bh);
    return NULL;
}

static struct buffer_head *add_entry(struct m_inode *dir,
    const char *name, int namelen, struct dir_entry **res_dir)
{
    int block, i;
    struct buffer_head *bh;
    struct dir_entry *de;

    *res_dir = NULL;

    if (namelen > NAME_LEN)
        namelen = NAME_LEN;
    if (!namelen)
        return NULL;
    block = dir->i_zone[0];
    if (!block)
        return NULL;
    bh = bread(dir->i_dev, block);
    if (!bh)
        return NULL;
    i = 0;  // 遍历的目录项数量
    de = (struct dir_entry *)bh->b_data;
    while (1) {
        if ((char *)de >= BLOCK_SIZE + bh->b_data) {
            brelse(bh);
            bh = NULL;
            block = create_block(dir, i / DIR_ENTRIES_PER_BLOCK);
            if (!block)
                return NULL;
            bh = bread(dir->i_dev, block);
            if (!bh) {
                i += DIR_ENTRIES_PER_BLOCK;
                continue;
            }
            de = (struct dir_entry *)bh->b_data;
        }
        if (i * sizeof(struct dir_entry) >= dir->i_size) {
            de->inode = 0;
            dir->i_size = (i + 1) * sizeof(struct dir_entry);
            dir->i_dirt = 1;
            dir->i_ctime = CURRENT_TIME;
        }
        if (!de->inode) {   // 未被使用的目录项
            dir->i_mtime = CURRENT_TIME;
            for (i = 0; i < NAME_LEN; i++)
                de->name[i] = (i < namelen) ? get_fs_byte(name + i) : 0;
            bh->b_dirt = 1;
            *res_dir = de;
            return bh;
        }
        de++;
        i++;
    }
    brelse(bh);
    return NULL;
}

static struct m_inode *get_dir(const char *pathname)
{
    char c;
    const char *thisname;
    struct m_inode *inode;
    struct buffer_head *bh;
    int namelen, inr, idev;
    struct dir_entry *de;

    if (!current->root || !current->root->i_count)
        panic("No root inode");
    if (!current->pwd || !current->pwd->i_count)
        panic("No cwd inode");
    
    c = get_fs_byte(pathname);
    if (c == '/') { // 第一个字符是/，说明是绝对路径
        inode = current->root;
        pathname++;
    }
    else if (c)   // 相对路径
        inode = current->pwd;
    else
        return NULL;
    
    inode->i_count++;
    while (1) {
        thisname = pathname;
        if (!S_ISDIR(inode->i_mode) || !permission(inode, MAY_EXEC)) {
            iput(inode);
            return NULL;
        }

        c = get_fs_byte(pathname++);
        for(namelen = 0; c && (c != '/'); namelen++)
            c = get_fs_byte(pathname++);
        if (!c)
            return inode;
        bh = find_entry(&inode, thisname, namelen, &de);
        if (!bh) {
            iput(inode);
            return NULL;
        }

        inr = de->inode;
        idev = inode->i_dev;
        brelse(bh);
        iput(inode);
        inode = iget(idev, inr);
        if (!inode)
            return NULL;
    }
}

/**
 * 通过路径名找到文件名、文件所在目录的inode，计算出文件名长度
 * @param pathname 文件的路径（绝对路径或相对路径）
 * @param namelen 通过指针得到文件名的长度
 * @param name 通过指针得到文件名
 * @return NULL：没有找到文件的目录；其他：文件目录的inode
 */
static struct m_inode *dir_namei(const char *pathname, int *namelen, const char **name)
{
    char c;
    const char *basename;
    struct m_inode *dir;

    dir = get_dir(pathname);
    if (!dir)
        return NULL;
    basename = pathname;
    do {
        c = get_fs_byte(pathname++);
        if (c == '/')
            basename = pathname;
    } while (c);
    *namelen = pathname - basename - 1; // 计算文件名长度
    *name = basename;   // 计算文件名起始地址
    return dir;
}

int open_namei(const char *pathname, int flag, int mode, struct m_inode **res_inode)
{
    const char *basename;
    int inr, dev, namelen;
    struct m_inode *dir, *inode;
    struct buffer_head *bh;
    struct dir_entry *de;

    if ((flag & O_TRUNC) && !(flag & O_ACCMODE))
        flag |= O_WRONLY;
    mode &= 0777 & ~current->umask;
    mode |= I_REGULAR;

    dir = dir_namei(pathname, &namelen, &basename);
    if (!dir)
        return -ENOENT;
    if (!namelen) {
        iput(dir);
        return -EISDIR;
    }
    
    bh = find_entry(&dir, basename, namelen, &de);
    if (!bh) {
        if (!(flag & O_CREAT)) {
            iput(dir);
            return -ENOENT; // No such file or directory
        }
        if (!permission(dir, MAY_WRITE)) {
            iput(dir);
            return -EACCES; // Permission denied
        }
        inode = new_inode(dir->i_dev);
        if (!inode) {
            iput(dir);
            return -ENOSPC; // No space left on device
        }
        inode->i_uid = current->euid;
        inode->i_mode = mode;
        inode->i_dirt = 1;
        bh = add_entry(dir, basename, namelen, &de);
        if (!bh) {
            inode->i_nlinks--;
            iput(inode);
            iput(dir);
            return -ENOSPC;
        }
        de->inode = inode->i_num;
        bh->b_dirt = 1;
        brelse(bh);
        iput(dir);
        *res_inode = inode;
        return 0;
    }
    
    inr = de->inode;
    dev = dir->i_dev;
    brelse(bh);
    iput(dir);

    inode = iget(dev, inr);
    if (!inode)
        return -EACCES;
    
    inode->i_atime = CURRENT_TIME;
    *res_inode = inode;
    return 0;
}

int sys_mkdir(const char *pathname, int mode)
{
    const char *basename;
    int namelen;
    struct m_inode *dir, *inode;
    struct buffer_head *bh, *dir_block;
    struct dir_entry *de;

    if (!suser())   // 只有超级用户有权限创建目录
        return -EPERM;
    dir = dir_namei(pathname, &namelen, &basename);
    if (!dir)
        return -ENOENT;
    if (!namelen) {
        iput(dir);
        return -ENOENT;
    }
    if (!permission(dir, MAY_WRITE)) {
        iput(dir);
        return -EPERM;
    }
    bh = find_entry(&dir, basename, namelen, &de);
    if (bh) {
        brelse(bh);
        iput(dir);
        return -EEXIST; // File exists
    }
    inode = new_inode(dir->i_dev);
    if (!inode) {
        iput(dir);
        return -ENOSPC;
    }
    inode->i_size = 32;
    inode->i_dirt = 1;
    inode->i_mtime = inode->i_atime = CURRENT_TIME;
    inode->i_zone[0] = new_block(inode->i_dev);
    if (!inode->i_zone[0]) {
        iput(dir);
        inode->i_nlinks--;
        iput(inode);
        return -ENOSPC;
    }
    inode->i_dirt = 1;
    dir_block = bread(inode->i_dev, inode->i_zone[0]);
    if (!dir_block) {
        iput(dir);
        free_block(inode->i_dev, inode->i_zone[0]);
        inode->i_nlinks--;
        iput(inode);
        return -ERROR;
    }
    de = (struct dir_entry *)dir_block->b_data;
    de->inode = inode->i_num;
    strcpy(de->name, ".");
    de++;
    de->inode = dir->i_num;
    strcpy(de->name, "..");
    inode->i_nlinks = 2;
    dir_block->b_dirt = 1;
    brelse(dir_block);
    inode->i_mode = I_DIRECTORY | (mode & 0777 & ~current->umask);
    inode->i_dirt = 1;
    bh = add_entry(dir, basename, namelen, &de);
    if (!bh) {
        iput(dir);
        free_block(inode->i_dev, inode->i_zone[0]);
        inode->i_nlinks = 0;
        iput(inode);
        return -ENOSPC;
    }
    de->inode = inode->i_num;
    bh->b_dirt = 1;
    dir->i_nlinks++;
    dir->i_dirt = 1;
    iput(dir);
    iput(inode);
    brelse(bh);
    return 0;
}

static int empty_dir(struct m_inode *inode)
{
    int nr, block;
    int len;
    struct buffer_head *bh;
    struct dir_entry *de;

    len = inode->i_size / sizeof(struct dir_entry);
    if (len < 2 || !inode->i_zone[0]) { // 目录项数至少为2，文件必须有逻辑块
        printk("warning - bad directory on dev %04x\n", inode->i_dev);
        return 0;
    }
    bh = bread(inode->i_dev, inode->i_zone[0]);
    if (!bh) {
        printk("warning - bad directory on dev %04x\n", inode->i_dev);
        return 0;
    }
    de = (struct dir_entry *)bh->b_data;
    if (de[0].inode != inode->i_num || !de[1].inode ||
            strcmp(".", de[0].name) || strcmp("..", de[1].name)) {
        printk("warning - bad directory on dev %04x\n", inode->i_dev);
        return 0;
    }
    nr = 2;
    de += 2;
    while (nr < len) {
        if ((void *)de >= (void *)(bh->b_data + BLOCK_SIZE)) {
            brelse(bh);
            block = bmap(inode, nr / DIR_ENTRIES_PER_BLOCK);
            if (!block) {
                nr += DIR_ENTRIES_PER_BLOCK;
                continue;
            }
            bh = bread(inode->i_dev, block);
            if (!bh)
                return 0;
            de = (struct dir_entry *)bh->b_data;
        }
        if (de->inode) {
            brelse(bh);
            return 0;
        }
        de++;
        nr++;
    }
    brelse(bh);
    return 1;
}

int sys_rmdir(const char *name)
{
    const char *basename;
    int namelen;
    struct m_inode *dir, *inode;
    struct buffer_head *bh;
    struct dir_entry *de;

    if (!suser())   // 只有超级用户有权限创建目录
        return -EPERM;
    dir = dir_namei(name, &namelen, &basename);
    if (!dir)
        return -ENOENT;
    if (!namelen) { // 目录名长度不能为0
        iput(dir);
        return -ENOENT;
    }
    if (!permission(dir, MAY_WRITE)) {    // 需要对上一级目录有写权限
        iput(dir);
        return -EPERM;
    }
    bh = find_entry(&dir, basename, namelen, &de);  // 查找目录项
    if (!bh) {
        iput(dir);
        return -ENOENT;
    }
    inode = iget(dir->i_dev, de->inode);    // 找到目录的inode
    if (!inode) {
        iput(dir);
        brelse(bh);
        return -EPERM;
    }
    if ((dir->i_mode & S_ISVTX) && current->euid &&
        inode->i_uid != current->euid) {
        iput(dir);
        iput(inode);
        brelse(bh);
        return -EPERM;
    }
    if (inode->i_dev != dir->i_dev || inode->i_count > 1) {
        iput(dir);
        iput(inode);
        brelse(bh);
        return -EPERM;
    }
    if (inode == dir) { // 不能使用"rmdir ."的形式删除目录
        iput(inode);
        iput(dir);
        brelse(bh);
        return -EPERM;
    }
    if (!S_ISDIR(inode->i_mode)) {    // 必须是目录
        iput(inode);
        iput(dir);
        brelse(bh);
        return -ENOTDIR;        // Not a directory
    }
    if (!empty_dir(inode)) {    // 目录中不能有文件
        iput(inode);
        iput(dir);
        brelse(bh);
        return -ENOTEMPTY;      // Directory not empty
    }
    if (inode->i_nlinks != 2)
        printk("empty directory has nlink!=2 (%d)", inode->i_nlinks);
    de->inode = 0;      // 该文件被删除
    bh->b_dirt = 1;
    brelse(bh);
    inode->i_nlinks = 0;
    inode->i_dirt = 1;
    dir->i_nlinks--;
    dir->i_ctime = dir->i_mtime = CURRENT_TIME;
    dir->i_dirt = 1;
    iput(dir);
    iput(inode);
    return 0;
}

int sys_unlink(const char *name)
{
    const char *basename;
    int namelen;
    struct m_inode *dir, *inode;
    struct buffer_head *bh;
    struct dir_entry *de;

    dir = dir_namei(name, &namelen, &basename);
    if (!dir)
        return -ENOENT; // No such file or directory
    if (!namelen) {     // 文件名长度不能为0
        iput(dir);
        return -ENOENT;
    }
    if (!permission(dir, MAY_WRITE)) {
        iput(dir);
        return -EPERM;  // Operation not permitted
    }
    bh = find_entry(&dir, basename, namelen, &de);
    if (!bh) {
        iput(dir);
        return -ENOENT;
    }
    inode = iget(dir->i_dev, de->inode);
    if (!inode) {
        iput(dir);
        brelse(bh);
        return -ENOENT;
    }
    if ((dir->i_mode & S_ISVTX) && !suser() &&
        current->euid != inode->i_uid &&
        current->euid != dir->i_uid) {
        iput(dir);
        iput(inode);
        brelse(bh);
        return -EPERM;
    }
    if (S_ISDIR(inode->i_mode)) {
        iput(inode);
        iput(dir);
        brelse(bh);
        return -EPERM;
    }
    if (!inode->i_nlinks) {
        printk("Deleting nonexistent file (%04x:%d), %d\n",
               inode->i_dev, inode->i_num, inode->i_nlinks);
        inode->i_nlinks = 1;
    }
    de->inode = 0;
    bh->b_dirt = 1;
    brelse(bh);
    inode->i_nlinks--;
    inode->i_dirt = 1;
    inode->i_ctime = CURRENT_TIME;
    iput(inode);
    iput(dir);
    return 0;
}
